From 8c3b360528a9ccd03f74649cf61eb6441de39037 Mon Sep 17 00:00:00 2001 From: Nick Cameron Date: Mon, 5 Dec 2016 17:15:46 -1000 Subject: [PATCH] Make some aspects of check/build available as an API. There are two key parts to this commit: * let API clients run `cargo check` with minimal fuss (ops/cargo_check.rs), * let API clients intercept and customise Cargo's calls to rustc (all the Executor stuff). --- src/bin/check.rs | 61 ++---------- src/cargo/core/shell.rs | 12 +++ src/cargo/ops/cargo_check.rs | 89 +++++++++++++++++ src/cargo/ops/cargo_compile.rs | 23 +++-- src/cargo/ops/cargo_install.rs | 4 +- src/cargo/ops/cargo_package.rs | 4 +- src/cargo/ops/cargo_rustc/fingerprint.rs | 9 +- src/cargo/ops/cargo_rustc/mod.rs | 119 +++++++++++++++-------- src/cargo/ops/mod.rs | 4 +- src/cargo/util/config.rs | 4 +- src/cargo/util/mod.rs | 2 +- 11 files changed, 217 insertions(+), 114 deletions(-) create mode 100644 src/cargo/ops/cargo_check.rs diff --git a/src/bin/check.rs b/src/bin/check.rs index 6d449f8d5..8ea6747cd 100644 --- a/src/bin/check.rs +++ b/src/bin/check.rs @@ -1,32 +1,9 @@ use std::env; -use cargo::core::Workspace; -use cargo::ops::{self, CompileOptions, MessageFormat}; -use cargo::util::important_paths::{find_root_manifest_for_wd}; +use cargo::ops::{self}; +use cargo::ops::cargo_check::{Options, with_check_env}; use cargo::util::{CliResult, Config}; -#[derive(RustcDecodable)] -pub struct Options { - flag_package: Vec, - flag_jobs: Option, - flag_features: Vec, - flag_all_features: bool, - flag_no_default_features: bool, - flag_target: Option, - flag_manifest_path: Option, - flag_verbose: u32, - flag_quiet: Option, - flag_color: Option, - flag_message_format: MessageFormat, - flag_release: bool, - flag_lib: bool, - flag_bin: Vec, - flag_example: Vec, - flag_test: Vec, - flag_bench: Vec, - flag_locked: bool, - flag_frozen: bool, -} pub const USAGE: &'static str = " Check a local package and all of its dependencies for errors @@ -69,35 +46,9 @@ the --release flag will use the `release` profile instead. pub fn execute(options: Options, config: &Config) -> CliResult> { debug!("executing; cmd=cargo-check; args={:?}", env::args().collect::>()); - config.configure(options.flag_verbose, - options.flag_quiet, - &options.flag_color, - options.flag_frozen, - options.flag_locked)?; - - let root = find_root_manifest_for_wd(options.flag_manifest_path, config.cwd())?; - - let opts = CompileOptions { - config: config, - jobs: options.flag_jobs, - target: options.flag_target.as_ref().map(|t| &t[..]), - features: &options.flag_features, - all_features: options.flag_all_features, - no_default_features: options.flag_no_default_features, - spec: ops::Packages::Packages(&options.flag_package), - mode: ops::CompileMode::Check, - release: options.flag_release, - filter: ops::CompileFilter::new(options.flag_lib, - &options.flag_bin, - &options.flag_test, - &options.flag_example, - &options.flag_bench), - message_format: options.flag_message_format, - target_rustdoc_args: None, - target_rustc_args: None, - }; - let ws = Workspace::new(&root, config)?; - ops::compile(&ws, &opts)?; - Ok(None) + with_check_env(options, config, |ws, opts| { + ops::compile(ws, opts)?; + Ok(None) + }) } diff --git a/src/cargo/core/shell.rs b/src/cargo/core/shell.rs index a28307874..58ed88f3f 100644 --- a/src/cargo/core/shell.rs +++ b/src/cargo/core/shell.rs @@ -62,6 +62,18 @@ impl MultiShell { MultiShell { out: out, err: err, verbosity: verbosity } } + // Create a quiet, basic shell from supplied writers. + pub fn from_write(out: Box, err: Box) -> MultiShell { + let config = ShellConfig { color_config: ColorConfig::Never, tty: false }; + let out = Shell { terminal: NoColor(out), config: config.clone() }; + let err = Shell { terminal: NoColor(err), config: config }; + MultiShell { + out: out, + err: err, + verbosity: Verbosity::Quiet, + } + } + pub fn out(&mut self) -> &mut Shell { &mut self.out } diff --git a/src/cargo/ops/cargo_check.rs b/src/cargo/ops/cargo_check.rs new file mode 100644 index 000000000..b8893efa9 --- /dev/null +++ b/src/cargo/ops/cargo_check.rs @@ -0,0 +1,89 @@ +use core::Workspace; +use ops::{self, CompileOptions, MessageFormat}; +use util::important_paths::{find_root_manifest_for_wd}; +use util::{CliResult, Config}; + +#[derive(RustcDecodable)] +pub struct Options { + flag_package: Vec, + flag_jobs: Option, + flag_features: Vec, + flag_all_features: bool, + flag_no_default_features: bool, + flag_target: Option, + flag_manifest_path: Option, + flag_verbose: u32, + flag_quiet: Option, + flag_color: Option, + flag_message_format: MessageFormat, + flag_release: bool, + flag_lib: bool, + flag_bin: Vec, + flag_example: Vec, + flag_test: Vec, + flag_bench: Vec, + flag_locked: bool, + flag_frozen: bool, +} + +impl Options { + pub fn default() -> Options { + Options { + flag_package: vec![], + flag_jobs: None, + flag_features: vec![], + flag_all_features: false, + flag_no_default_features: false, + flag_target: None, + flag_manifest_path: None, + flag_verbose: 0, + flag_quiet: None, + flag_color: None, + flag_message_format: MessageFormat::Human, + flag_release: false, + flag_lib: false, + flag_bin: vec![], + flag_example: vec![], + flag_test: vec![], + flag_bench: vec![], + flag_locked: false, + flag_frozen: false, + } + } +} + + +pub fn with_check_env(options: Options, config: &Config, f: F) -> CliResult> + where F: FnOnce(&Workspace, &CompileOptions) -> CliResult> +{ + config.configure(options.flag_verbose, + options.flag_quiet, + &options.flag_color, + options.flag_frozen, + options.flag_locked)?; + + let root = find_root_manifest_for_wd(options.flag_manifest_path, config.cwd())?; + + let opts = CompileOptions { + config: config, + jobs: options.flag_jobs, + target: options.flag_target.as_ref().map(|t| &t[..]), + features: &options.flag_features, + all_features: options.flag_all_features, + no_default_features: options.flag_no_default_features, + spec: ops::Packages::Packages(&options.flag_package), + mode: ops::CompileMode::Check, + release: options.flag_release, + filter: ops::CompileFilter::new(options.flag_lib, + &options.flag_bin, + &options.flag_test, + &options.flag_example, + &options.flag_bench), + message_format: options.flag_message_format, + target_rustdoc_args: None, + target_rustc_args: None, + }; + + let ws = Workspace::new(&root, config)?; + f(&ws, &opts) +} diff --git a/src/cargo/ops/cargo_compile.rs b/src/cargo/ops/cargo_compile.rs index 1be87408a..cce8fc2b6 100644 --- a/src/cargo/ops/cargo_compile.rs +++ b/src/cargo/ops/cargo_compile.rs @@ -27,7 +27,7 @@ use std::path::PathBuf; use core::{Source, Package, Target}; use core::{Profile, TargetKind, Profiles, Workspace, PackageIdSpec}; -use ops::{self, BuildOutput}; +use ops::{self, BuildOutput, Executor, DefaultExecutor}; use util::config::Config; use util::{CargoResult, profile}; @@ -113,18 +113,26 @@ pub enum CompileFilter<'a> { pub fn compile<'a>(ws: &Workspace<'a>, options: &CompileOptions<'a>) -> CargoResult> { + compile_with_exec(ws, options, &mut DefaultExecutor) +} + +pub fn compile_with_exec<'a, E: Executor>(ws: &Workspace<'a>, + options: &CompileOptions<'a>, + exec: &mut E) + -> CargoResult> { for member in ws.members() { for key in member.manifest().warnings().iter() { options.config.shell().warn(key)? } } - compile_ws(ws, None, options) + compile_ws(ws, None, options, exec) } -pub fn compile_ws<'a>(ws: &Workspace<'a>, - source: Option>, - options: &CompileOptions<'a>) - -> CargoResult> { +pub fn compile_ws<'a, E: Executor>(ws: &Workspace<'a>, + source: Option>, + options: &CompileOptions<'a>, + exec: &mut E) + -> CargoResult> { let CompileOptions { config, jobs, target, spec, features, all_features, no_default_features, release, mode, message_format, @@ -231,7 +239,8 @@ pub fn compile_ws<'a>(ws: &Workspace<'a>, &resolve_with_overrides, config, build_config, - profiles)? + profiles, + exec)? }; ret.to_doc_test = to_builds.iter().map(|&p| p.clone()).collect(); diff --git a/src/cargo/ops/cargo_install.rs b/src/cargo/ops/cargo_install.rs index 5a144fc0f..ce7285e21 100644 --- a/src/cargo/ops/cargo_install.rs +++ b/src/cargo/ops/cargo_install.rs @@ -13,7 +13,7 @@ use toml; use core::{SourceId, Source, Package, Dependency, PackageIdSpec}; use core::{PackageId, Workspace}; -use ops::{self, CompileFilter}; +use ops::{self, CompileFilter, DefaultExecutor}; use sources::{GitSource, PathSource, SourceConfigMap}; use util::{CargoResult, ChainError, Config, human, internal}; use util::{Filesystem, FileLock}; @@ -106,7 +106,7 @@ pub fn install(root: Option<&str>, check_overwrites(&dst, pkg, &opts.filter, &list, force)?; } - let compile = ops::compile_ws(&ws, Some(source), opts).chain_error(|| { + let compile = ops::compile_ws(&ws, Some(source), opts, &mut DefaultExecutor).chain_error(|| { if let Some(td) = td_opt.take() { // preserve the temporary directory, so the user can inspect it td.into_path(); diff --git a/src/cargo/ops/cargo_package.rs b/src/cargo/ops/cargo_package.rs index e27646d9f..8436fc380 100644 --- a/src/cargo/ops/cargo_package.rs +++ b/src/cargo/ops/cargo_package.rs @@ -11,7 +11,7 @@ use tar::{Archive, Builder, Header}; use core::{SourceId, Package, PackageId, Workspace, Source}; use sources::PathSource; use util::{self, CargoResult, human, internal, ChainError, Config, FileLock}; -use ops; +use ops::{self, DefaultExecutor}; pub struct PackageOpts<'cfg> { pub config: &'cfg Config, @@ -298,7 +298,7 @@ fn run_verify(ws: &Workspace, tar: &File, opts: &PackageOpts) -> CargoResult<()> mode: ops::CompileMode::Build, target_rustdoc_args: None, target_rustc_args: None, - })?; + }, &mut DefaultExecutor)?; Ok(()) } diff --git a/src/cargo/ops/cargo_rustc/fingerprint.rs b/src/cargo/ops/cargo_rustc/fingerprint.rs index 3d1f27449..dde13d4c2 100644 --- a/src/cargo/ops/cargo_rustc/fingerprint.rs +++ b/src/cargo/ops/cargo_rustc/fingerprint.rs @@ -148,11 +148,10 @@ impl Fingerprint { fn update_local(&self) -> CargoResult<()> { match self.local { LocalFingerprint::MtimeBased(ref slot, ref path) => { - let meta = fs::metadata(path).chain_error(|| { - internal(format!("failed to stat `{}`", path.display())) - })?; - let mtime = FileTime::from_last_modification_time(&meta); - *slot.0.lock().unwrap() = Some(mtime); + if let Ok(meta) = fs::metadata(path) { + let mtime = FileTime::from_last_modification_time(&meta); + *slot.0.lock().unwrap() = Some(mtime); + } } LocalFingerprint::Precalculated(..) => return Ok(()) } diff --git a/src/cargo/ops/cargo_rustc/mod.rs b/src/cargo/ops/cargo_rustc/mod.rs index 24c3de656..23107efab 100644 --- a/src/cargo/ops/cargo_rustc/mod.rs +++ b/src/cargo/ops/cargo_rustc/mod.rs @@ -11,7 +11,7 @@ use rustc_serialize::json; use core::{Package, PackageId, PackageSet, Target, Resolve}; use core::{Profile, Profiles, Workspace}; use core::shell::ColorConfig; -use util::{self, CargoResult, ProcessBuilder, human, machine_message}; +use util::{self, CargoResult, ProcessBuilder, ProcessError, human, machine_message}; use util::{Config, internal, ChainError, profile, join_paths, short_hash}; use self::job::{Job, Work}; @@ -55,16 +55,47 @@ pub struct TargetConfig { pub type PackagesToBuild<'a> = [(&'a Package, Vec<(&'a Target, &'a Profile)>)]; +/// A glorified callback for executing calls to rustc. Rather than calling rustc +/// directly, we'll use an Executor, giving clients an opportunity to intercept +/// the build calls. +pub trait Executor: Clone + Send + 'static { + fn init(&mut self, cx: &Context); + /// If execution succeeds, the ContinueBuild value indicates whether Cargo + /// should continue with the build process for this package. + fn exec(&self, cmd: ProcessBuilder, id: &PackageId) -> Result; +} + +/// A DefaultExecutorcalls rustc without doing anything else. It is Cargo's +/// default behaviour. +#[derive(Copy, Clone)] +pub struct DefaultExecutor; + +impl Executor for DefaultExecutor { + fn init(&mut self, _cx: &Context) {} + + fn exec(&self, cmd: ProcessBuilder, _id: &PackageId) -> Result { + cmd.exec()?; + Ok(ContinueBuild::Continue) + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum ContinueBuild { + Continue, + Stop, +} + // Returns a mapping of the root package plus its immediate dependencies to // where the compiled libraries are all located. -pub fn compile_targets<'a, 'cfg: 'a>(ws: &Workspace<'cfg>, - pkg_targets: &'a PackagesToBuild<'a>, - packages: &'a PackageSet<'cfg>, - resolve: &'a Resolve, - config: &'cfg Config, - build_config: BuildConfig, - profiles: &'a Profiles) - -> CargoResult> { +pub fn compile_targets<'a, 'cfg: 'a, E: Executor>(ws: &Workspace<'cfg>, + pkg_targets: &'a PackagesToBuild<'a>, + packages: &'a PackageSet<'cfg>, + resolve: &'a Resolve, + config: &'cfg Config, + build_config: BuildConfig, + profiles: &'a Profiles, + exec: &mut E) + -> CargoResult> { let units = pkg_targets.iter().flat_map(|&(pkg, ref targets)| { let default_kind = if build_config.requested_target.is_some() { Kind::Target @@ -97,7 +128,7 @@ pub fn compile_targets<'a, 'cfg: 'a>(ws: &Workspace<'cfg>, // part of this, that's all done next as part of the `execute` // function which will run everything in order with proper // parallelism. - compile(&mut cx, &mut queue, unit)?; + compile(&mut cx, &mut queue, unit, exec)?; } // Now that we've figured out everything that we're going to do, do it! @@ -167,9 +198,10 @@ pub fn compile_targets<'a, 'cfg: 'a>(ws: &Workspace<'cfg>, Ok(cx.compilation) } -fn compile<'a, 'cfg: 'a>(cx: &mut Context<'a, 'cfg>, - jobs: &mut JobQueue<'a>, - unit: &Unit<'a>) -> CargoResult<()> { +fn compile<'a, 'cfg: 'a, E: Executor>(cx: &mut Context<'a, 'cfg>, + jobs: &mut JobQueue<'a>, + unit: &Unit<'a>, + exec: &mut E) -> CargoResult<()> { if !cx.compiled.insert(*unit) { return Ok(()) } @@ -188,7 +220,7 @@ fn compile<'a, 'cfg: 'a>(cx: &mut Context<'a, 'cfg>, let work = if unit.profile.doc { rustdoc(cx, unit)? } else { - rustc(cx, unit)? + rustc(cx, unit, exec)? }; let link_work1 = link_targets(cx, unit)?; let link_work2 = link_targets(cx, unit)?; @@ -202,13 +234,13 @@ fn compile<'a, 'cfg: 'a>(cx: &mut Context<'a, 'cfg>, // Be sure to compile all dependencies of this target as well. for unit in cx.dep_targets(unit)?.iter() { - compile(cx, jobs, unit)?; + compile(cx, jobs, unit, exec)?; } Ok(()) } -fn rustc(cx: &mut Context, unit: &Unit) -> CargoResult { +fn rustc(cx: &mut Context, unit: &Unit, exec: &mut E) -> CargoResult { let crate_types = unit.target.rustc_crate_types(); let mut rustc = prepare_rustc(cx, crate_types, unit)?; @@ -257,6 +289,10 @@ fn rustc(cx: &mut Context, unit: &Unit) -> CargoResult { .flat_map(|i| i) .map(|s| s.to_string()) .collect::>(); + + exec.init(cx); + let exec = exec.clone(); + return Ok(Work::new(move |state| { // Only at runtime have we discovered what the extra -L and -l // arguments are for native libraries, so we process those here. We @@ -290,7 +326,7 @@ fn rustc(cx: &mut Context, unit: &Unit) -> CargoResult { } state.running(&rustc); - if json_messages { + let cont = if json_messages { rustc.exec_with_streaming( &mut |line| if !line.is_empty() { Err(internal(&format!("compiler stdout is not empty: `{}`", line))) @@ -316,32 +352,37 @@ fn rustc(cx: &mut Context, unit: &Unit) -> CargoResult { } Ok(()) }, - ).map(|_| ()) + ).map(|_| ()).chain_error(|| { + human(format!("Could not compile `{}`.", name)) + })?; + ContinueBuild::Continue } else { - rustc.exec() - }.chain_error(|| { - human(format!("Could not compile `{}`.", name)) - })?; + exec.exec(rustc, &package_id).chain_error(|| { + human(format!("Could not compile `{}`.", name)) + })? + }; - if do_rename && real_name != crate_name { - let dst = &filenames[0].0; - let src = dst.with_file_name(dst.file_name().unwrap() - .to_str().unwrap() - .replace(&real_name, &crate_name)); - if !has_custom_args || src.exists() { - fs::rename(&src, &dst).chain_error(|| { - internal(format!("could not rename crate {:?}", src)) - })?; + if cont == ContinueBuild::Continue { + if do_rename && real_name != crate_name { + let dst = &filenames[0].0; + let src = dst.with_file_name(dst.file_name().unwrap() + .to_str().unwrap() + .replace(&real_name, &crate_name)); + if !has_custom_args || src.exists() { + fs::rename(&src, &dst).chain_error(|| { + internal(format!("could not rename crate {:?}", src)) + })?; + } } - } - if !has_custom_args || fs::metadata(&rustc_dep_info_loc).is_ok() { - info!("Renaming dep_info {:?} to {:?}", rustc_dep_info_loc, dep_info_loc); - fs::rename(&rustc_dep_info_loc, &dep_info_loc).chain_error(|| { - internal(format!("could not rename dep info: {:?}", - rustc_dep_info_loc)) - })?; - fingerprint::append_current_dir(&dep_info_loc, &cwd)?; + if !has_custom_args || fs::metadata(&rustc_dep_info_loc).is_ok() { + info!("Renaming dep_info {:?} to {:?}", rustc_dep_info_loc, dep_info_loc); + fs::rename(&rustc_dep_info_loc, &dep_info_loc).chain_error(|| { + internal(format!("could not rename dep info: {:?}", + rustc_dep_info_loc)) + })?; + fingerprint::append_current_dir(&dep_info_loc, &cwd)?; + } } if json_messages { diff --git a/src/cargo/ops/mod.rs b/src/cargo/ops/mod.rs index 865b74094..0b59fb923 100644 --- a/src/cargo/ops/mod.rs +++ b/src/cargo/ops/mod.rs @@ -1,10 +1,11 @@ pub use self::cargo_clean::{clean, CleanOptions}; -pub use self::cargo_compile::{compile, compile_ws, CompileOptions}; +pub use self::cargo_compile::{compile, compile_with_exec, compile_ws, CompileOptions}; pub use self::cargo_compile::{CompileFilter, CompileMode, MessageFormat, Packages}; pub use self::cargo_read_manifest::{read_manifest,read_package,read_packages}; pub use self::cargo_rustc::{compile_targets, Compilation, Kind, Unit}; pub use self::cargo_rustc::Context; pub use self::cargo_rustc::{BuildOutput, BuildConfig, TargetConfig}; +pub use self::cargo_rustc::{Executor, DefaultExecutor, ContinueBuild}; pub use self::cargo_run::run; pub use self::cargo_install::{install, install_list, uninstall}; pub use self::cargo_new::{new, init, NewOptions, VersionControl}; @@ -23,6 +24,7 @@ pub use self::cargo_pkgid::pkgid; pub use self::resolve::{resolve_ws, resolve_ws_precisely, resolve_with_previous}; pub use self::cargo_output_metadata::{output_metadata, OutputMetadataOptions, ExportInfo}; +pub mod cargo_check; mod cargo_clean; mod cargo_compile; mod cargo_doc; diff --git a/src/cargo/util/config.rs b/src/cargo/util/config.rs index 598a6dccc..63382c10c 100644 --- a/src/cargo/util/config.rs +++ b/src/cargo/util/config.rs @@ -47,7 +47,7 @@ impl Config { rustdoc: LazyCell::new(), extra_verbose: Cell::new(false), frozen: Cell::new(false), - locked: Cell::new(false), + locked: Cell::new(false), } } @@ -635,7 +635,7 @@ impl fmt::Display for Definition { } } -fn homedir(cwd: &Path) -> Option { +pub fn homedir(cwd: &Path) -> Option { let cargo_home = env::var_os("CARGO_HOME").map(|home| { cwd.join(home) }); diff --git a/src/cargo/util/mod.rs b/src/cargo/util/mod.rs index d1195d75c..0f9e92051 100644 --- a/src/cargo/util/mod.rs +++ b/src/cargo/util/mod.rs @@ -1,5 +1,5 @@ pub use self::cfg::{Cfg, CfgExpr}; -pub use self::config::Config; +pub use self::config::{Config, homedir}; pub use self::dependency_queue::{DependencyQueue, Fresh, Dirty, Freshness}; pub use self::errors::{CargoResult, CargoError, ChainError, CliResult}; pub use self::errors::{CliError, ProcessError, CargoTestError}; -- 2.30.2